# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )

from jsonrpc.jmsghandler import RequestHandler
from jsonrpc.sendmsg import sendjmsg
from jsonrpc import Server
from time import sleep, time
from threading import Thread, RLock
from argparse import ArgumentParser
import random

__all__ = ['TaskHandler', 'TaskServer', 'TaskRequest']

#---------------------------------------------

class TaskHandler(RequestHandler):
    """
    Do some tasks, maybe in subprocess
    """
    
    def _statistics_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed
        
        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False
        
        request = self.server.getStatisticsRequest()

        if json_data["task"] == "ping":
            return request.ping()
        if json_data["task"] == "quit":
            return request.quit()
        if json_data["task"] == "reset":
            return request.reset()
        if json_data["task"] == "inc":
            json_data = json_data["data"]
            return request.inc(json_data["param"], json_data["value"])
        if json_data["task"] == "dec":
            json_data = json_data["data"]
            return request.dec(json_data["param"], json_data["value"])
        if json_data["task"] == "set":
            json_data = json_data["data"]
            return request.set(json_data["param"], json_data["value"])
        if json_data["task"] == "is_updated":
            return request.is_updated()
        if json_data["task"] == "get_update":
            return request.get_update()
        if json_data["task"] == "set_hashing":
            json_data = json_data["data"]
            return request.set_hashing(
                json_data["device"],
                json_data["name"],
                json_data["size"],
                json_data["readed"],
                json_data["start_time"]
            )
        if json_data["task"] == "inc_hashing":
            json_data = json_data["data"]
            return request.inc_hashing(
                json_data["device"],
                json_data["readed"]
            )
        if json_data["task"] == "set_imaging":
            json_data = json_data["data"]
            return request.set_imaging(
                json_data["name"],
                json_data["count"],
                json_data["processed"],
                json_data["start_time"]
            )
        if json_data["task"] == "inc_imaging":
            json_data = json_data["data"]
            return request.inc_imaging(
                json_data["processed"]
            )
        return result


    def _options_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        request = self.server.getOptionsRequest()

        if json_data["task"] == "ping":
            return request.ping()
        if json_data["task"] == "quit":
            return request.quit()
        if json_data["task"] == "set":
            json_data = json_data["data"]
            return request.set(json_data["param"], json_data["value"])
        if json_data["task"] == "get":
            json_data = json_data["data"]
            return request.get(json_data["param"])
        return result


    def _hash_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        if json_data["task"] == "ping":
            json_data = json_data["data"]
            request = self.server.getHashRequest(json_data["device"])
            return request.ping(json_data["device"])
        if json_data["task"] == "quit":
            request = self.server.getHashRequest(None)
            return request.quit()
        if json_data["task"] == "hash":
            json_data = json_data["data"]
            request = self.server.getHashRequest(json_data["device"])
            return request.hash(
                json_data["file_id"],
                json_data["device"],
                json_data["file_size"],
                json_data["full_path"]
            )
        return result


    def _check_file_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        if json_data["task"] == "ping":
            json_data = json_data["data"]
            request = self.server.getCheckFileRequest(json_data["device"])
            return request.ping(json_data["device"])
        if json_data["task"] == "quit":
            request = self.server.getCheckFileRequest(None)
            return request.quit()
        if json_data["task"] == "check":
            json_data = json_data["data"]
            request = self.server.getCheckFileRequest(json_data["device"])
            return request.check(
                json_data["device"],
                json_data["dir_id"],
                json_data["parent_path"],
                json_data["name"]
            )
        return result


    def _scan_directory_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        if json_data["task"] == "ping":
            json_data = json_data["data"]
            request = self.server.getScanDirectoryRequest(json_data["device"])
            return request.ping(json_data["device"])
        if json_data["task"] == "quit":
            request = self.server.getScanDirectoryRequest(None)
            return request.quit()
        if json_data["task"] == "scan":
            json_data = json_data["data"]
            request = self.server.getScanDirectoryRequest(json_data["device"])
            return request.scan(
                json_data["device"],
                json_data["dir_id"],
                json_data["parent_path"],
                json_data["name"]
            )
        return result


    def _process_archive_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        request = self.server.getProcessArchiveRequest()

        if json_data["task"] == "ping":
            return request.ping()
        if json_data["task"] == "quit":
            return request.quit()
        if json_data["task"] == "process":
            json_data = json_data["data"]
            return request.process(
                json_data["file_id"],
                json_data["file_path"],
                json_data["mime"]
            )
        return result


    def _json_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed

        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False

        if not (type(json_data) is dict):
            return result

        # print("TaskHandler: data=%r" % (json_data,))

        if json_data["service"] is None:
            if json_data["task"] == "ping":
                return "pong"
            if json_data["task"] == "stop":
                return self.server.stop()
            if json_data["task"] == "quit":
                return self.server.quit()
        if json_data["service"] == "statistics":
            result = self._statistics_handle(json_data)
        if json_data["service"] == "options":
            result = self._options_handle(json_data)
        if json_data["service"] == "hash":
            result = self._hash_handle(json_data)
        if json_data["service"] == "check_file":
            result = self._check_file_handle(json_data)
        if json_data["service"] == "scan_directory":
            result = self._scan_directory_handle(json_data)
        if json_data["service"] == "process_archive":
            result = self._process_archive_handle(json_data)

        return result

    pass

#---------------------------------------------

class TaskServer(Server):
    
    _options_manager = None
    _statistics_manager = None
    _hash_manager = None
    _check_file_manager = None
    _scan_directory_manager = None
    _process_archive_manager = None

    _server_options_lock = None
    _server_statistics_lock = None
    _server_hash_lock = None
    _server_check_file_lock = None
    _server_scan_directory_lock = None
    _server_process_archive_lock = None

    def server_activate(self):
        self._server_options_lock = RLock()
        self._server_statistics_lock = RLock()
        self._server_hash_lock = RLock()
        self._server_check_file_lock = RLock()
        self._server_scan_directory_lock = RLock()
        self._server_process_archive_lock = RLock()
        return Server.server_activate(self)


    def getOptionsManager(self):

        self._server_options_lock.acquire()

        if not self._options_manager:

            from service.options import OptionsManager

            self._options_manager = OptionsManager()
            self._options_manager.start_service()

        self._server_options_lock.release()

        return self._options_manager

    def getOptionsRequest(self):
        return self.getOptionsManager().get_request()


    def getStatisticsManager(self):

        self._server_statistics_lock.acquire()

        if not self._statistics_manager:

            from service.statistics import StatisticsManager

            self._statistics_manager = StatisticsManager()
            self._statistics_manager.start_service()

        self._server_statistics_lock.release()

        return self._statistics_manager

    def getStatisticsRequest(self):
        return self.getStatisticsManager().get_request()


    def getProcessArchiveManager(self):

        self._server_process_archive_lock.acquire()

        if not self._process_archive_manager:

            from service.process_archive import ProcessArchiveManager

            self._process_archive_manager = ProcessArchiveManager(self.server_address)
            self._process_archive_manager.start_service()

        self._server_process_archive_lock.release()

        return self._process_archive_manager

    def getProcessArchiveRequest(self):
        return self.getProcessArchiveManager().get_request()


    def getHashManager(self, device):

        self._server_hash_lock.acquire()

        if not self._hash_manager:

            from service.hash import HashManager

            self._hash_manager = HashManager(self.server_address)

        if device:
            self._hash_manager.start_service(device)

        self._server_hash_lock.release()

        return self._hash_manager

    def getHashRequest(self, device):
        return self.getHashManager(device).get_request()


    def getCheckFileManager(self, device):

        self._server_check_file_lock.acquire()

        if not self._check_file_manager:

            from service.check_file import CheckFileManager

            self._check_file_manager = CheckFileManager(self.server_address)

        if device:
            self._check_file_manager.start_service(device)

        self._server_check_file_lock.release()

        return self._check_file_manager

    def getCheckFileRequest(self, device):
        return self.getCheckFileManager(device).get_request()


    def getScanDirectoryManager(self, device):

        self._server_scan_directory_lock.acquire()

        if not self._scan_directory_manager:

            from service.scan_directory import ScanDirectoryManager

            self._scan_directory_manager = ScanDirectoryManager(self.server_address)

        if device:
            self._scan_directory_manager.start_service(device)

        self._server_scan_directory_lock.release()

        return self._scan_directory_manager

    def getScanDirectoryRequest(self, device):
        return self.getScanDirectoryManager(device).get_request()


    def stop(self, all=False):

        if all:
            if self._options_manager:
                self.getOptionsRequest().quit()
                self._options_manager = None

            if self._statistics_manager:
                self.getStatisticsRequest().quit()
                self._statistics_manager = None

        if self._hash_manager:
            self.getHashRequest(None).quit()
            self._hash_manager = None

        if self._scan_directory_manager:
            self.getScanDirectoryRequest(None).quit()
            self._scan_directory_manager = None

        if self._check_file_manager:
            self.getCheckFileRequest(None).quit()
            self._check_file_manager = None

        if self._process_archive_manager:
            self.getProcessArchiveRequest().quit()
            self._process_archive_manager = None

        return

    def _quit_thread(self):
        sleep(0.05)
        self.server_close()
        return

    def quit(self):
        print("TaskServer: quit event")

        self.stop(True)

        th = Thread(target = self._quit_thread)
        th.daemon = True
        th.start()
        return True

    pass

#---------------------------------------------


class StatisticsTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self):
        return self.manager.sendmsg("statistics", "ping", {})

    def reset(self):
        return self.manager.sendmsg("statistics", "reset", {})

    def inc(self, param, value):
        return self.manager.sendmsg("statistics", "inc", {
            "param" : param,
            "value" : value
        })

    def dec(self, param, value):
        return self.manager.sendmsg("statistics", "dec", {
            "param" : param,
            "value" : value
        })

    def set(self, param, value):
        return self.manager.sendmsg("statistics", "set", {
            "param" : param,
            "value" : value
        })

    def is_updated(self):
        return self.manager.sendmsg("statistics", "is_updated", {})

    def get_update(self):
        return self.manager.sendmsg("statistics", "get_update", {})

    def set_hashing(self, device, name, size, readed, start_time):
        return self.manager.sendmsg("statistics", "set_hashing", {
            "device" : device,
            "name" : name,
            "size" : size,
            "readed" : readed,
            "start_time" : start_time
        })

    def inc_hashing(self, device, readed):
        return self.manager.sendmsg("statistics", "inc_hashing", {
            "device" : device,
            "readed" : readed
        })

    def set_imaging(self, name, count, processed, start_time):
        return self.manager.sendmsg("statistics", "set_imaging", {
            "name" : name,
            "count" : count,
            "processed" : processed,
            "start_time" : start_time
        })

    def inc_imaging(self, processed):
        return self.manager.sendmsg("statistics", "inc_imaging", {
            "processed" : processed
        })

    def quit(self):
        return self.manager.sendmsg("statistics", "quit", {})

    pass


class OptionsTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self):
        return self.manager.sendmsg("options", "ping", {})

    def reset(self):
        return self.manager.sendmsg("options", "reset", {})

    def set(self, param, value):
        return self.manager.sendmsg("options", "set", {
            "param" : param,
            "value" : value
        })

    def get(self, param):
        return self.manager.sendmsg("options", "get", {
            "param" : param
        })

    def quit(self):
        return self.manager.sendmsg("options", "quit", {})

    pass


class HashTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "hash", "ping", {"device":device})

    def hash(self, file_id, device, file_size, full_path):
        return self.manager.sendmsg( "hash", "hash", {
            "file_id" : file_id,
            "device" : device,
            "file_size" : file_size,
            "full_path" : full_path
        })

    def quit(self):
        return self.manager.sendmsg( "hash", "quit", {})

    pass


class CheckFileTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "check_file", "ping", {"device":device})

    def check(self, device, dir_id, parent_path, name):
        return self.manager.sendmsg( "check_file", "check", {
            "device" : device,
            "dir_id" : dir_id,
            "parent_path" : parent_path,
            "name" : name
        })

    def quit(self):
        return self.manager.sendmsg( "check_file", "quit", {})

    pass


class ScanDirectoryTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "scan_directory", "ping", {"device":device})

    def scan(self, device, dir_id, parent_path, name):
        return self.manager.sendmsg( "scan_directory", "scan", {
            "device" : device,
            "dir_id" : dir_id,
            "parent_path" : parent_path,
            "name" : name
        })

    def quit(self):
        return self.manager.sendmsg( "scan_directory", "quit", {})

    pass


class ProcessArchiveTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self):
        return self.manager.sendmsg( "process_archive", "ping", {})

    def process(self, file_id, file_path, mime):
        return self.manager.sendmsg( "process_archive", "process", {
            "file_id" : file_id,
            "file_path" : file_path,
            "mime" : mime
        })

    def quit(self):
        return self.manager.sendmsg( "process_archive", "quit", {})

    pass



class TaskRequest(object):

    _options = None
    _statistics = None
    _hash = None
    _check_file = None
    _scan_directory = None
    _process_archive = None

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def getStatistics(self):
        if not self._statistics:
            self._statistics = StatisticsTaskRequest(self.manager)
        return self._statistics

    def getOptions(self):
        if not self._options:
            self._options = OptionsTaskRequest(self.manager)
        return self._options

    def getCheckFile(self):
        if not self._check_file:
            self._check_file = CheckFileTaskRequest(self.manager)
        return self._check_file

    def getScanDirectory(self):
        if not self._scan_directory:
            self._scan_directory = ScanDirectoryTaskRequest(self.manager)
        return self._scan_directory

    def getHash(self):
        if not self._hash:
            self._hash = HashTaskRequest(self.manager)
        return self._hash

    def getProcessArchive(self):
        if not self._process_archive:
            self._process_archive = ProcessArchiveTaskRequest(self.manager)
        return self._process_archive

    def ping(self):
        return self.manager.sendmsg(None, "ping", {})

    def stop(self):
        return self.manager.sendmsg(None, "stop", {})

    def quit(self):
        return self.manager.sendmsg(None, "quit", {})

    pass


#---------------------------------------------

class TaskManager(object):

    host = "127.0.1.1"
    port_min = 10000
    port_max = 20000
    port = None
    request = None

    address = None

    def __init__(self, address = None):
        self.address = address
        pass

    def start_service(self):

        from archive import which
        from subprocess import Popen

        self.port = random.randint(self.port_min, self.port_max)

        self.address = (self.host, self.port,)

        Popen([which("python3"), "service/task.py",
               "--host", "%s" % self.host,
               "--port", "%s" % self.port
        ])

        # WAIT WHILE SERVICE STARTS

        pings = 10
        runing = False
        t_begin = time()
        while not runing:
            pong = self.sendmsg(None, "ping", {})

            if pong == "pong":
                runing = True

            pings -= 1
            if pings <= 0:
                break

        if pings <= 0:
            self.port = None
            print("TaskManager: FAIL TO START service on (%s, %s) in %s sec" % (self.host, self.port, time() - t_begin,))
        else:
            print("TaskManager: STARTED service on (%s, %s) in %s sec" % (self.host, self.port, time() - t_begin,))

        return runing

    def sendmsg(self, service, task, data):
        count = 10
        t_wait = 1.5
        result = None
        while count > 0:
            t_begin = time()
            try:
                result = sendjmsg( self.address, {"service":service, "task":task, "data":data}, timeout=t_wait )
                if result is None:
                    raise ValueError("Result is NONE!")

                break
            except Exception as e:
                print("TaskManager[%s]: SOCKET: %r" % (count, e,))
                result = None
                count -= 1
                t_end = time()
                print("               : t_end=%r, t_begin=%r, t_wait=%r" % (
                    t_end, t_begin, t_wait
                ))
                if t_end - t_begin < t_wait:
                    print("               : sleep by ~ t_wait - (t_end - t_begin)=%r" % (
                        t_wait - (t_end - t_begin)
                        ))
                    sleep(t_wait - (t_end - t_begin))
        if count <= 0:
            print("TaskManager: FAIL SEND MESSAGE: service=%s, task=%s, data=%r" % (service, task, data,))
        return result

    def quit(self):
        return self.sendmsg(None, "quit", {})

    def get_request(self):
        if not self.request:
            self.request = TaskRequest(self)
        return self.request

    pass


#---------------------------------------------

def run_task_service(host, port):

    try:
        SERVER_ADDRESS = (host, int(port),)

        server = TaskServer(SERVER_ADDRESS, TaskHandler, bind_and_activate=False)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception as e:
        print("run_task_service: ERROR=%r" % (e,))
        pass

    return


def launch():

    parser = ArgumentParser(description='Run task service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')

    args = parser.parse_args()
    run_task_service(args.host, args.port)

    return 0

if __name__ == "__main__":
    launch()
